import requests
import base64
import json
import os
from urllib.parse import unquote,quote
import re
import time

class CongfigPath():
    seat_type_map_dic = {
        "1": "hard_seat",
        "9": "business_seat",
        "4": "soft_bed",
        "6": "vip_soft_bed",
        "0": "second_seat",
        "WZ": "no_seat",
        "3": "hard_bed",
        "F": "move_bed",
        "M": "first_seat"
    }

    @staticmethod
    def getStationPath():
        current_path = os.path.realpath(__file__)
        # 将路径名分成左右两个，左边是路径名，右边是文件名，所有取第一个索引，就是路径
        current_dir = os.path.split(current_path)[0]
        return current_dir + r"\stations.json"

    @staticmethod
    def getCaptchaPath():
        current_path = os.path.realpath(__file__)
        # 将路径名分成左右两个，左边是路径名，右边是文件名，所有取第一个索引，就是路径
        current_dir = os.path.split(current_path)[0]
        return current_dir + r"\captcha.jpg"


class APItool():
    session = requests.session()
    query_dic={}

    @classmethod
    def downloadCaptcha(cls):
        # 方式一  不建议使用 太过繁琐
        # 模拟请求链接 得到文本内容 截取图片地址 再次使用图片地址访问 保存图片
        # response = cls.session.get(r'https://kyfw.12306.cn/passport/captcha/captcha-image64?login_site=E&module=login&rand=sjrand')
        # dic = response.json()
        # # print(dic)
        # req = base64.b64decode(dic['image'])    # 通过base64解码，将图片翻译出来
        # with open("captcha.jpg", "wb") as f:
        #     f.write(req)

        # 方式二
        # 直接将base64的加密字符去除掉，删除后面参数，直接获取图片
        # 直接在后面拼接参数
        # yzm_url = "https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand"
        # 通过表单（字典）给get请求拼接参数
        # get用params，post用data

        # 获取验证码 get
        get_captcha_url = r"https://kyfw.12306.cn/passport/captcha/captcha-image?"
        # 获取验证码的参数
        get_parameter = {
            "login_site": "E",
            "module": "login",
            "rand": "sjrand"
        }

        response = cls.session.get(get_captcha_url, params=get_parameter)

        captcha_file_path = CongfigPath.getCaptchaPath()
        with open(captcha_file_path, "wb") as f:  # 将图片写入文件保存起来
            f.write(response.content)

        return captcha_file_path

    @classmethod
    def checkCaptcha(cls, result):
        # 检查验证码 get
        check_captcha_url = r"https://kyfw.12306.cn/passport/captcha/captcha-check"
        # 检查验证码参数
        check_parameter = {
            "answer": result,
            "rand": "sjrand",
            "login_site": "E"
        }
        check_result = cls.session.get(check_captcha_url, params=check_parameter)  # 获取验证结果
        # cls.cookie=str(cls.session.cookies.get_dict())  # 获取当前的cookies
        # print(cls.cookie)

        # print(check_result.text)
        # print(check_result)
        dic = check_result.json()  # 使用json函数，讲字符串转换为字典类型
        return dic["result_code"] == "4"  # 返回验证码是否为 4

    @classmethod
    def checkAccount(cls, account, password):
        # 登录验证 post
        check_account_url = r"https://kyfw.12306.cn/passport/web/login"
        account_parameter = {
            "username": account,
            "password": password,
            "appid": "otn",
            'if_check_slide_passcode_token':''
        }
        login_result = cls.session.post(check_account_url, data=account_parameter)
        print(login_result.text)
        print(cls.session.cookies)
        result_code = login_result.json()["result_code"]
        if result_code == 0:
            cls.author()
            return "".join(cls.getHello())
        else:
            return None

    @classmethod
    def getHello(cls):
        # 问好
        get_hello_url = "https://kyfw.12306.cn/otn/index/initMy12306Api"
        print("获取问好")
        response = cls.session.post(get_hello_url, cookies=cls.session.cookies)
        dic = response.json()
        if dic["status"]==200:
            return (dic["data"]["user_name"] + dic["data"]["user_ragard"])
        return ("","")

    @classmethod
    def author(cls):
        uamtk_url = "https://kyfw.12306.cn/passport/web/auth/uamtk"
        response = cls.session.post(uamtk_url, data={"appid": "otn"})
        tk = response.json()["newapptk"]
        uamauthclient_url = "https://kyfw.12306.cn/otn/uamauthclient"
        res2 = cls.session.post(uamauthclient_url, data={"tk": tk})
        print(res2.text)
        # cls.getHello()

    @staticmethod
    def getStationName():
        # 1.检查本地是否有缓存
        # 2.如果有，直接加载，返回出去
        if os.path.exists(CongfigPath.getStationPath()):
            print("读取缓存")
            with open(CongfigPath.getStationPath(), "r", encoding="utf-8") as f:
                # result=json.loads(f.read(),encoding="utf-8")  #转换字符串为json型
                result = json.load(f, encoding="utf-8")  # 解码文件为json
                # print(type(result))
                # print(result)
                return result
        else:
            print("网络请求下载站点数据")
            # 3.如果没有，发送网络请求，保存到本地
            station_dic = {}
            # 获取所有城市
            station_name_url = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9147"
            reps = requests.get(station_name_url)
            items = reps.text.split("@")
            for item in items:
                station_list = item.split("|")
                if len(station_list) != 6:
                    continue
                # print(station_list)
                city_name = station_list[1]
                city_code = station_list[2]
                station_dic[city_name] = city_code

            # print(station_dic)
            with open(CongfigPath.getStationPath(), "w", encoding="utf-8") as f:
                json.dump(station_dic, f)
            return station_dic

    @staticmethod
    def getStationNameReverse():
        dic = APItool.getStationName()
        reverse_dic = {value: key for key, value in dic.items()}
        return reverse_dic

    @classmethod
    def queryTickets(cls,train_date, from_station, to_station, purpose_codes, seat_type=None):
        cls.query_dic={
            "train_date":train_date,
            "purpose_codes":purpose_codes,
            "seat_type":seat_type
        }
        # 获取反转后的字典值
        code_station_reverse = APItool.getStationNameReverse()
        # 查询车票地址
        query_tickets_url = "https://kyfw.12306.cn/otn/leftTicket/query"
        parameter = {
            "leftTicketDTO.train_date": train_date,
            "leftTicketDTO.from_station": from_station,
            "leftTicketDTO.to_station": to_station,
            "purpose_codes": purpose_codes
        }
        header = {
            "Referer": "https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-origin",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
        }
        resp = requests.get(query_tickets_url, params=parameter, headers=header)
        result = resp.json()
        train_dicts = []
        if result["httpstatus"] == 200:
            items = result["data"]["result"]
            for item in items:
                trainDict = {}
                train_dicts.append(trainDict)
                trainInfo = item.split('|')
                if trainInfo[11] == 'Y':  # 是否可以预定
                    trainDict['secret_str'] = trainInfo[0]  # 车次密文字符串(下订单时使用)
                    trainDict['train_num'] = trainInfo[2]  # 车次编号 24000000Z30L
                    trainDict['train_name'] = trainInfo[3]  # 车次名称，如D352
                    trainDict['from_station_code'] = trainInfo[6]  # 出发地电报码
                    trainDict['to_station_code'] = trainInfo[7]  # 目的地地电报码
                    trainDict['from_station_name'] = code_station_reverse[trainInfo[6]]  # 出发地名称 手动实现 北京
                    trainDict['to_station_name'] = code_station_reverse[trainInfo[7]]  # 目的地名称 手动实现 上海
                    trainDict['start_time'] = trainInfo[8]  # 出发时间
                    trainDict['arrive_time'] = trainInfo[9]  # 到达时间
                    trainDict['total_time'] = trainInfo[10]  # 总用时
                    trainDict['left_ticket'] = trainInfo[
                        12]  # 余票 wrlmtI6BmBd8izygoiCBbpr3%2B%2BGKdIk1SHpJdJ1f6w1p%2FhGF
                    trainDict['train_date'] = trainInfo[13]  # 火车日期 20190121
                    trainDict['train_location'] = trainInfo[15]  # P4 后期用
                    trainDict["vip_soft_bed"] = trainInfo[21]  # 高级软卧
                    trainDict['other_seat'] = trainInfo[22]  # 其他
                    trainDict["soft_bed"] = trainInfo[23]  # 软卧
                    trainDict["no_seat"] = trainInfo[26]  # 无座
                    trainDict["hard_bed"] = trainInfo[28]  # 硬卧
                    trainDict["hard_seat"] = trainInfo[29]  # 硬座
                    trainDict["second_seat"] = trainInfo[30]  # 二等座
                    trainDict["first_seat"] = trainInfo[31]  # 一等座
                    trainDict["business_seat"] = trainInfo[32]  # 商务座
                    trainDict["move_bed"] = trainInfo[33]  # 动卧
                    if seat_type is None:
                        train_dicts.append(trainDict)
                    else:
                        key = CongfigPath.seat_type_map_dic[seat_type]
                        if trainDict[key] == "有" or trainDict[key].isdigit():
                            train_dicts.append(trainDict)
                print(trainDict)
        else:
            print("数据请求错误")
        return train_dicts

    @classmethod
    def checkLogin(cls):
        # 验证用户是否登录 post
        is_login_url = "https://kyfw.12306.cn/otn/login/checkUser"
        rep = cls.session.post(is_login_url, data={"_json_att": ""})
        is_login = rep.json()["data"]["flag"]
        return is_login

    @classmethod
    def buyTicket(cls,trainDict=None):
        if not cls.checkLogin():
            print("请先登录")
            return False
        print(trainDict)
        if not cls.submitOrderRequest(trainDict):
            print("提交订单失败")
            return False
        token,key_check_isChange =cls.initDc()
        passenger=cls.getPassengerDTOs(token)# 获取当前登录用户信息作为乘客信息字典
        if not cls.checkOrderInfo(cls.query_dic["seat_type"],passenger,token):
            print("检查订单失败")
            return False
        if not cls.getQueueCount(trainDict,cls.query_dic["seat_type"],token):
            print("查询队列个数失败")
            return False
        if not cls.confirmSingleForQueue(passenger,token,cls.query_dic["seat_type"],trainDict,key_check_isChange):
            print("预定失败")
            return False
        return True



    @classmethod
    def submitOrderRequest(cls,trainDict):
        # 提交订单信息 post
        submit_order_request_url = "https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest"
        data = {
            "secretStr": unquote(trainDict["secretStr"]),  # 数据经过url加密，需要解码才能使用
            "train_date": cls.query_dic["train_date"],
            "back_train_date": cls.query_dic["train_date"],
            "tour_flag": "dc",
            "purpose_codes": cls.query_dic["purpose_codes"],
            "query_from_station_name": trainDict["from_station_name"],
            "query_to_station_name": trainDict["to_station_name"],
            "undefined": ""
        }
        rep = cls.session.post(submit_order_request_url, data=data)
        status = rep.json()["status"]
        if not status:
            print(rep.json()["messages"])
            return False
        print("继续订票")
        return True

    @classmethod
    def initDc(cls):
        # 获取token 用户信息验证码
        initDc_url="https://kyfw.12306.cn/otn/confirmPassenger/initDc"
        rep = cls.session.post(initDc_url,data={"_json_att":""})
        try:
            # print(rep.text) # 获取到的数据是一个网页，非结构化数据，要使用正则表达式来获取内容
            token=re.findall( r"var globalRepeatSubmitToken = '(.*?)'",rep.text)[0]
            key_check_isChange=re.findall( r"'key_check_isChange':'(.*?)'",rep.text)[0]
            return (token,key_check_isChange)
        except:
            return None

    @classmethod
    def getPassengerDTOs(cls,token):
        get_passenger_DTO_url="https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs"
        data={
            "_json_att":"",
            "REPEAT_SUBMIT_TOKEN": token
        }
        rep = cls.session.post(get_passenger_DTO_url,data=data)
        result=rep.json()
        if not result["status"]:
            print("获取乘客信息失败")
            return None
        # 获取登录的用户姓名作为乘客乘客姓名
        name=cls.getHello()[0]
        for dic in result["data"]["normal_passengers"]:
            if dic["passengers_name"]==name:
                return dic
        return None

    @classmethod
    def checkOrderInfo(cls,seat_type,passenger_dir,token):
        check_order_info_url="https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo"
        data={
            "cancel_flag": "2",
            "bed_level_order_num": "000000000000000000000000000000",
            "passengerTicketStr": "{},{},{},{},{},{},{},N".format(seat_type,passenger_dir["passenger_flag"],passenger_dir["passenger_type"],passenger_dir["passenger_name"],passenger_dir["passenger_id_type_code"],passenger_dir["passenger_id_no"],passenger_dir["mobile_no"]),
            "oldPassengerStr": "{},{},{},1_".format(passenger_dir["passenger_name"],passenger_dir["passenger_id_type_code"],passenger_dir["passenger_id_no"]),
            "tour_flag": "dc",
            "randCode":"",
            "whatsSelect": "1",
            "sessionId":"",
            "sig":"",
            "scene": "nc_login",
            "_json_att":"",
            "REPEAT_SUBMIT_TOKEN": token
        }
        rep = cls.session.post(check_order_info_url,data=data)
        print(rep.text)
        result = rep.json()
        if result["status"] and result["data"]["submit_status"]:
            print("检查订单成功订单")
            return True

    @classmethod
    def getQueueCount(cls,trainDic,seat_type,token):
        def timeFormat(train_date):# 格式化时间
            time_arr=time.strftime(train_date, "%Y%m%d")
            time_temp=time.mktime(time_arr)
            time_local=time.localtime(time_temp)
            format="%a %b %d %Y %H:%M:%S GMT+0800 (China Standara Time)"
            return time.strftime(format,time_local)

        # 获取队列个数
        get_queue_count_url="https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount"
        data={
            "train_date": timeFormat(trainDic["train_date"]),
            "train_no": trainDic["train_num"],
            "stationTrainCode": trainDic["train_name"],
            "seatType": seat_type,
            "fromStationTelecode": trainDic["from_station_code"],
            "toStationTelecode": trainDic["to_station_code"],
            "leftTicket": trainDic["left_ticket"],
            "purpose_codes": "00",
            "train_location": trainDic["train_location"],
            "_json_att":"",
            "REPEAT_SUBMIT_TOKEN": token
        }
        rep = cls.session.post(get_queue_count_url,data=data)
        result=rep.json()
        if not result["status"]:
            print("查询队列消息失败")
            return False
        print("查询队列消息成功",result["data"]["ticket"])
        return True

    @classmethod
    def confirmSingleForQueue(cls,passenger_dir,token,seat_type,trainDic,key_check_isChange):
        # 确认订单
        confirm_url="https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue"
        data={
            "passengerTicketStr": "{},{},{},{},{},{},{},N".format(seat_type,passenger_dir["passenger_flag"],passenger_dir["passenger_type"],passenger_dir["passenger_name"],passenger_dir["passenger_id_type_code"],passenger_dir["passenger_id_no"],passenger_dir["mobile_no"]),
            "oldPassengerStr": "{},{},{},1_".format(passenger_dir["passenger_name"],passenger_dir["passenger_id_type_code"],passenger_dir["passenger_id_no"]),
            "randCode":"",
            "purpose_codes": "00",
            "key_check_isChange": key_check_isChange,
            "leftTicketStr": trainDic["left_ticket"],
            "train_location": trainDic["train_location"],
            "choose_seats":"",
            "seatDetailType": "000",
            "whatsSelect": "1",
            "roomType": "00",
            "dwAll": "N",
            "_json_att":"",
            "REPEAT_SUBMIT_TOKEN":token
        }
        rep=cls.session.post(confirm_url,data=data)
        result=rep.json()
        print(result)
        return result["status"] and result["data"]["submitStatus"]



if __name__ == '__main__':
    APItool.getStationName()


#https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue
#passengerTicketStr: 4,0,1,杨海权,1,4416***********030,130****1100,N,a49b4365c8506f454911c9de2738479d6c18c980286829ed98fe38ff172eb5824bfaff358b2b95bd0cf5386396d050efbeee5c1307bd2381fcf7151eb0cebc544a76746eb2a05700c60e204a01a89ea8a7868894b601e9cb9a172acca6e21c92
# oldPassengerStr: 杨海权,1,4416***********030,1_
# randCode:
# purpose_codes: 00
# key_check_isChange: 9841750E918BC6D2D4F1ED2A5BB22F5E01B51EFC81265377058EA03F
# leftTicketStr: t4W2rQFou8jH3g3EROo5uXgL97Gwh7mwjPU9aYfJiHnFYIr7JAQ8Z5YcNeY%3D
# train_location: Q6
# choose_seats:
# seatDetailType: 000
# whatsSelect: 1
# roomType: 00
# dwAll: N
# _json_att:
# REPEAT_SUBMIT_TOKEN: 9756524810d773ced5aea44b27a40341

#https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random=1594006071132&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN=9756524810d773ced5aea44b27a40341
#random: 1594006071132
# tourFlag: dc
# _json_att:
# REPEAT_SUBMIT_TOKEN: 9756524810d773ced5aea44b27a40341
